package upmc.api.aladyn.server.services;

import java.io.*;
import java.lang.reflect.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javassist.*;

import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;

import upmc.api.aladyn.server.interfaces.*;

/**
 * Class for creating and reading the content which comes from the client
 * 
 * The content is using the format of XML
 * 
 * 
 * @author Z_WANG
 * 
 */
public class CreateOrReadXMLMethods {
	ClassPool poolParent = ClassPool.getDefault();
	
// region Private Methods
	/**
	 * Method to test the input content whether the datatime iso8601
	 * @param text
	 * @return boolean, T=> datatime iso8601
	 */
	private boolean isDateTimeIso8601Type(String text){
        String reg = "[0-9]{4}[0-1][0-9][0-3][0-9]T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]";
        Pattern p=Pattern.compile(reg);
        Matcher m = p.matcher(text);
        while(m.find()){
            return true;
        }
        return false;
	}
	/**To mathch the architecture of XML-RMI, get the value final for the client
	 * 
	 * @param returnValue
	 * @param cMethod
	 * @return
	 */
	private String GetMethodResponseValue(Object returnValue, CtMethod cMethod) {

		String methodResponseValue = "";
		try {
			String returnTypeValue = cMethod.getReturnType().getSimpleName();
			
			System.out.println("return type " +returnTypeValue);
			
			// "void"
			if(returnTypeValue.equals("void"))
			{
				methodResponseValue = "void";
			}
			// "String"
			else if(returnTypeValue.equals("String"))
			{
				methodResponseValue = (String) returnValue;
			}
			// "Boolean"
			else if(returnTypeValue.equals("Boolean"))
			{
				if((Boolean)returnValue)
				{
					methodResponseValue = "1";
				}
				else
				{
					methodResponseValue = "0";
				}
			}
			// "int"
			else if(returnTypeValue.equals("int"))
			{
				methodResponseValue = returnValue+"";
			}
				
						
		} catch (NotFoundException e) {
			e.printStackTrace();
		}
		return methodResponseValue;
		
		
	}
	
	/**Get the response of the 'Object' for the client, ex: its fields
	 * 
	 * @param cl =>Source
	 * @return 
	 */
	private String GetMethodResponseObject(Class<?> cl){
		
		StringBuffer sb = new StringBuffer();
		try{
			
			CtClass ctClassCopy =poolParent.getCtClass(cl.getName());
			
			Method  getterOid = cl.getDeclaredMethod("getOid", new Class[]{});
			Object getteroidValue = getterOid.invoke(cl.newInstance(), new Object[]{});
			
			System.out.println("****************      "+getteroidValue);
			
			sb.append("<object oid=\""+getteroidValue+"\"><fields>");
			
			// get the fields
			CtField[] ctFields = ctClassCopy.getDeclaredFields();
			if(ctFields != null && ctFields.length > 0)
			{
				for(int m = 0; m<ctFields.length; m++)
				{
					CtField ctField = ctFields[m];
					
					String filedName = ctField.getName();
					if(!filedName.equals("oid"))
					{
						String filedType = ctField.getType().getSimpleName();
						String firstLetter = filedName.substring(0,1).toUpperCase();
						
						String getMethodName = "get"+firstLetter+filedName.substring(1);
						
						// to get the value of the parameter by its getter
						Method  getterMethod = cl.getDeclaredMethod(getMethodName, new Class[]{});					
						Object getterValue = getterMethod.invoke(cl.newInstance(), new Object[]{});
						
						
						
						if(filedType.equals("Object") || filedType.equals("object"))
						{
							
							Class<?> fieldObjectClassType = getterValue.getClass();
											
														
							sb.append("<field name=\""+filedName+"\"><value>"+GetMethodResponseObject(fieldObjectClassType)+"</value></field>");
						}
						else if(filedType.equals("Boolean"))
						{
							if((Boolean)getterValue){
								sb.append("<field name=\""+filedName+"\"><value><"+filedType.toLowerCase()+">1</"+filedType.toLowerCase()+"></value></field>");
							}else{
								sb.append("<field name=\""+filedName+"\"><value><"+filedType.toLowerCase()+">0</"+filedType.toLowerCase()+"></value></field>");
							}
						}
						else if(filedType.equals("Object[]"))
						{
							sb.append("<field name=\""+filedName+"\">");
							Object[] values = (Object[])getterValue;
							
							for(int item = 0; item < values.length; item++)
							{
								Object value = values[item];
								String valueClassName = value.getClass().getSimpleName();
								sb.append("<value>");
								
								if(valueClassName.equals("String"))
								{
									if(this.isDateTimeIso8601Type((String)value)){
										sb.append("<dateTime.iso8601>"+value+"</dateTime.iso8601>");
									}else{
										sb.append("<string>"+value+"</string>");
									}
								}
								else if(valueClassName.equals("Double"))
								{
									sb.append("<double>"+value+"</double>");
								}
								else if(valueClassName.equals("Integer"))
								{
									sb.append("<int>"+value+"</int>");
								}
								else if(valueClassName.equals("Boolean"))
								{
									if((Boolean)value)
									{
										sb.append("<boolean>"+1+"</boolean>");
									}
									else
									{
										sb.append("<boolean>"+0+"</boolean>");
									}
									
								}
								
								
								sb.append("</value>");
							}
							sb.append("</field>");
							
						}
						else if(filedType.equals("String")){
							if(this.isDateTimeIso8601Type((String)getterValue))
							{
								sb.append("<field name=\""+filedName+"\"><value><dateTime.iso8601>"+getterValue+"</dateTime.iso8601></value></field>");
							}else{
								sb.append("<field name=\""+filedName+"\"><value><string>"+getterValue+"</string></value></field>");
							}
						}
						else
						{
							sb.append("<field name=\""+filedName+"\"><value><"+filedType.toLowerCase()+">"+getterValue+"</"+filedType.toLowerCase()+"></value></field>");
						}
					}
					
				}
			}
			sb.append("</fields><methods></methods></object>");
		}catch (Exception e) {
			e.printStackTrace();
		}
		
		return sb.toString();
	}
	
	/** Get the CtClass by the Node "Object"
	 * 
	 * @param nodeObject Node 'Object'
	 * @param ctClass 
	 * @return CtClass
	 */
	private CtClass GetCtClassByNodeObject(Node nodeObject, CtClass ctClass){
		
		try{
			
			System.out.println("Class Name : "+ ctClass.getName());
			
			// oid of the object
			String oidString = nodeObject.getAttributes().getNamedItem("oid")
					.getNodeValue();
			String oidFiledValue = "private String oid = \""+ oidString + "\";";
			CtField oidFiled = CtField.make(oidFiledValue,ctClass);
			ctClass.addField(oidFiled);
			
			String getterOidValue = "public String getOid(){return oid;}";
			CtMethod getterOid = CtNewMethod.make(getterOidValue.toString(),ctClass);
			ctClass.addMethod(getterOid);
			
			// Type of the object, ex: Stringable
			String classType = nodeObject.getAttributes()
					.getNamedItem("type").getNodeValue();
			
			// ctclass of the interface we will use, ex: Stringable
			// the interfaces are at the package 'upmc.api.aladyn.server.interfaces'
			if(!classType.equals("")){
				System.out.println("Input class type : "+classType);
				CtClass ctClassInterface = poolParent
						.getCtClass("upmc.api.aladyn.server.interfaces."
								+ classType);
		
				// set interface to the class
				// now, there is only one interface
				ctClass.setInterfaces(new CtClass[] { ctClassInterface });
			}
	
			// Content of the object ==> fields and methods
			for (Node nde = nodeObject.getFirstChild(); nde != null; nde = nde
					.getNextSibling()) {
				
				// fields
				if (nde.getNodeType() == Node.ELEMENT_NODE
						&& nde.getNodeName().equals("fields")) {
					for (Node ndeField = nde.getFirstChild(); ndeField != null; ndeField = ndeField
							.getNextSibling()) {
						// each field
						if (ndeField.getNodeType() == Node.ELEMENT_NODE
								&& ndeField.getNodeName().equals(
										"field")) {
							
							// define the getter and setter for the field
							StringBuffer getterField = new StringBuffer();
							StringBuffer setterField = new StringBuffer();
							
							getterField.append("public ");									
							setterField.append("public  void ");
							
							Boolean filedIsDateTimeType = false;
							
							// Filed's name ex: x y mark
							String nameValue = ndeField.getAttributes()
									.getNamedItem("name")
									.getNodeValue();
	
							String firstLetter = nameValue.substring(0,1).toUpperCase();
							setterField.append("set"+firstLetter+nameValue.substring(1));
							
							
							// type of the field and its value ex:
							// mark==> String @
							StringBuffer filedValue = new StringBuffer();
							
							if(ndeField.getChildNodes() != null && ndeField.getChildNodes().getLength() >0 && ndeField.getChildNodes().getLength() <2)
							{
								Node nodeFieldValue = ndeField.getLastChild();
								Node nodeFieldType = nodeFieldValue.getLastChild();
								System.out.println("************     "+nodeFieldType.getNodeName());
								
								
								// conditions of the type
								if (nodeFieldType.getNodeName().equals("string")) {
									getterField.append(" String ");
									setterField.append("(String "+nameValue +"){");
									filedValue.append("private String "
											+ nameValue
											+ " = \""
											+ nodeFieldType.getFirstChild()
													.getNodeValue() + "\";");
								}
								else if (nodeFieldType.getNodeName().equals("object")) {

									
									CtClass fieldObjectCtClass = poolParent
											.makeClass("upmc.api.aladyn.server.services."+firstLetter+nameValue.substring(1));
									
									fieldObjectCtClass = GetCtClassByNodeObject(nodeFieldType,fieldObjectCtClass);
									
									fieldObjectCtClass.toClass();

									getterField.append(" Object ");
									setterField.append("(Object "+nameValue +"){");
									filedValue.append("private Object "
											+ nameValue+" = new upmc.api.aladyn.server.services."+firstLetter+nameValue.substring(1)+"();");
								}
								else if(nodeFieldType.getNodeName().equals("boolean")){
									getterField.append(" Boolean ");
									setterField.append("(Boolean "+nameValue +"){");
									if(nodeFieldType.getFirstChild().getNodeValue().equals("1")){
										filedValue.append("private Boolean "
												+ nameValue
												+ " = true;");
									}
									else if(nodeFieldType.getFirstChild().getNodeValue().equals("0")){
										filedValue.append("private Boolean "
												+ nameValue
												+ " = false;");
									}
								}
								else if(nodeFieldType.getNodeName().equals("dateTime.iso8601")){
									getterField.append(" String ");
									setterField.append("(java.util.Date "+nameValue +"){");
									setterField.append("String dateString = new java.text.SimpleDateFormat(\"yyyyMMdd HH:mm:ss\").format("+nameValue+");");
									setterField.append("this."+nameValue+" = dateString.replace(' ', 'T');}");
									filedValue.append("private String "
											+ nameValue
											+ " = \""
											+ nodeFieldType.getFirstChild()
													.getNodeValue() + "\";");
								}
								else {
									getterField.append(" " + nodeFieldType.getNodeName()+ " ");
									setterField.append("("+nodeFieldType.getNodeName()+" "+nameValue +"){");
									filedValue.append("private "
											+ nodeFieldType.getNodeName()
											+ " "
											+ nameValue
											+ " = "
											+ nodeFieldType.getFirstChild()
													.getNodeValue() + ";");
								}
								
							}
							else if(ndeField.getChildNodes() != null && ndeField.getChildNodes().getLength() > 1)
							{								
								getterField.append(" Object[] ");
								setterField.append("(Object[] "+nameValue +"){");
								
								//filedValue.append("private Object[] "+nameValue+" = new Object[]{\"1\",\"2\",new Integer(3),new Boolean(\"True\")};");
								filedValue.append("private Object[] "+nameValue+" = new Object[]{");
								NodeList ndeFieldValues = ndeField.getChildNodes();
								
								StringBuffer valuesOfArray = new StringBuffer();
								
								for(int item = 0; item < ndeFieldValues.getLength(); item++)
								{
									
									Node nodeFieldValue = ndeFieldValues.item(item);
									Node nodeFieldType = nodeFieldValue.getLastChild();
									
									
									if (nodeFieldType.getNodeName().equals("object")) {

										CtClass fieldArrayValueObjectCtClass = poolParent
												.makeClass("upmc.api.aladyn.server.services."+firstLetter+nameValue.substring(1)+item);
										
										fieldArrayValueObjectCtClass = GetCtClassByNodeObject(nodeFieldType,fieldArrayValueObjectCtClass);
										
										fieldArrayValueObjectCtClass.toClass();

										valuesOfArray.append(" new upmc.api.aladyn.server.services."+firstLetter+nameValue.substring(1)+item+"(),");
									}
									else if(nodeFieldType.getNodeName().equals("boolean"))
									{
										if(nodeFieldType.getFirstChild().getNodeValue().equals("1")){
											valuesOfArray.append("new Boolean(\"True\"),");
										}
										else if(nodeFieldType.getFirstChild().getNodeValue().equals("0")){
											valuesOfArray.append("new Boolean(\"False\"),");
										}
									}
									else if (nodeFieldType.getNodeName().equals("string"))
									{
										valuesOfArray.append("\""+nodeFieldType.getFirstChild().getNodeValue()+"\",");
									}
									else if (nodeFieldType.getNodeName().equals("int") || nodeFieldType.getNodeName().equals("i4"))
									{
										valuesOfArray.append("new Integer("+nodeFieldType.getFirstChild().getNodeValue()+"),");
									}
									else if (nodeFieldType.getNodeName().equals("double"))
									{
										valuesOfArray.append("new Double("+nodeFieldType.getFirstChild().getNodeValue()+"),");
									}
									else if (nodeFieldType.getNodeName().equals("dateTime.iso8601"))
									{
										valuesOfArray.append("\""+nodeFieldType.getFirstChild().getNodeValue()+"\",");
									}
								}
								filedValue.append(valuesOfArray.toString().substring(0, valuesOfArray.toString().length()-1)+"};");
							}
							
							
							getterField.append("get"+firstLetter+nameValue.substring(1)+"()");
							getterField.append("{return "+nameValue +";}");
							
							if(!filedIsDateTimeType){
								setterField.append("this."+nameValue+" = "+nameValue+";}");
							}
							
							// delete after
							System.out.println("ooooo        "
									+ filedValue.toString());
							System.out.println("getter        "
									+ getterField.toString());
							System.out.println("setter        "
									+ setterField.toString());
							
							// add the field
							CtField f = CtField.make(filedValue.toString(),ctClass);
							ctClass.addField(f); 
							
							// add the method 'getter' of the field
							CtMethod getter = CtNewMethod.make(getterField.toString(),ctClass);
							ctClass.addMethod(getter);
							
							// add the method 'setter' of the field
							CtMethod setter = CtNewMethod.make(setterField.toString(),ctClass);
							ctClass.addMethod(setter);
							
							
						}
					}
	
				}
				
				// methods
				if (nde.getNodeType() == Node.ELEMENT_NODE
						&& nde.getNodeName().equals("methods")) {
					for (Node ndeMethod = nde.getFirstChild(); ndeMethod != null; ndeMethod = ndeMethod
							.getNextSibling()) {
						// add the method
						CtMethod cm = CtNewMethod.make(ndeMethod
								.getFirstChild().getNodeValue(),
								ctClass);
	
						ctClass.addMethod(cm);
					}
				}
			}

			ctClass.writeFile();
			
		}catch (Exception e) {
			e.printStackTrace();
		}
		return ctClass;
	}


	/**Call the method client wants to, and get the reponse of the method
	 * 
	 * @param appelMethod name of the method 
	 * @param cl =>Class<?>
	 * @return => response of the method
	 */
	private String GenerateMethodReponse(String appelMethod, Class<?> cl) {
		
		StringBuffer sb = new StringBuffer();// the return value
		
		try {
			Boolean flag = false; // flag to use the called method; F=> non, T=> yes			
			String classType = "";
			Object returnValue = null;
			
			CtClass ctClass = poolParent.get(cl.getName());

			// Get the class which includes all methods called, 
			// always using the same class 'upmc.api.aladyn.server.services.MethodsCollection'
			CtClass cc = poolParent.get("upmc.api.aladyn.server.services.MethodsCollection");
			
			// get the method called by client
			CtMethod cMethod = cc.getDeclaredMethod(appelMethod);
			
			// get the parameters' type of the method called
			CtClass[] methodParameterypes = cMethod.getParameterTypes();
			
			Class<?>[] classMethodParameterypes = new Class[]{};
			
			// to test whether the type of the object is same with the type of parameter of the method called
			if(methodParameterypes != null && methodParameterypes.length > 0 &&methodParameterypes.length <2)
			{
				classMethodParameterypes = new Class[methodParameterypes.length];
				for(int i =0; i < methodParameterypes.length; i++)
				{
					CtClass ccMethodParameterype = methodParameterypes[i];
				 	System.out.println(ccMethodParameterype.getName());
				 	
				 	classMethodParameterypes[i] = Class.forName((ccMethodParameterype.getName()));
				 	
				 	CtClass[] classInterfaces = ctClass.getInterfaces();
				 	for(int j =0; j < classInterfaces.length; j++)
					{
				 		CtClass ccInterface = methodParameterypes[j];
				 		if(ccInterface.getName().equals(ccMethodParameterype.getName()))
				 		{
				 			flag = true; // the correct
				 			classType = ccInterface.getName(); // get the type of the object
				 		}
					}
				}
			}
			
			
			if(flag)
			{
				sb.append("<methodResponse><params><param><value>");

				//Class<?> classMethodsCollection = cc.toClass();
				Class<?> classMethodsCollection = Class.forName("upmc.api.aladyn.server.services.MethodsCollection");
				
				// get the method called
				Method methodToUse = classMethodsCollection.getMethod(appelMethod, classMethodParameterypes);
				
				// Interface of Stringable
				
				System.out.println("Class Type : "+classType);
				
				if(classType.equals("upmc.api.aladyn.server.interfaces.Stringable"))
				{
					Stringable client = (Stringable) cl.newInstance();				
					
					// get the return value by invoking the method called
					returnValue = methodToUse.invoke(classMethodsCollection.newInstance(),client);					
				}
				
				// Because there are some conditions of the return type of the method called
				// so we should call the method 'GetMethodResponseValue' first to get the correct return value
				sb.append(GetMethodResponseValue(returnValue,cMethod)+"</value></param><param><value>" );
				
				sb.append(GetMethodResponseObject(cl));
				
				
				sb.append("</value></param></params></methodResponse>");
			}
			
		} catch (NotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return sb.toString();
	}

// endregion

//	region Public Methods

	/**Read the xml and generate the Class, and the get the response for the client
	 * 
	 * @param strXMLRMI => XML(MethodCall)
	 * @return reponse of Methodresponse
	 */
	public String ReadXMLAndGenerateReponse(String strXMLRMI) {
		try {

			String appelMethod = "";
			Date currentTime = new Date();   
            SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");   
            String dateString = formatter.format(currentTime); 
            System.out.println(dateString);
            
			CtClass ctClass = poolParent
					.makeClass("upmc.api.aladyn.server.services.Client"+dateString); // Create a new CtClass

			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document doc = builder.parse(new InputSource(new StringReader(
					strXMLRMI)));
			Element root = doc.getDocumentElement();

			// Methods' Name, we want to use the method which is in the server
			// ex: display
			NodeList nodesMethodName = root.getElementsByTagName("methodName");
			if (nodesMethodName != null && nodesMethodName.getLength() > 0) {
				for (int i = 0; i < nodesMethodName.getLength(); i++) {
					Node nodeMethodName = nodesMethodName.item(i);

					appelMethod = nodeMethodName.getFirstChild().getNodeValue();
				}
			}
			
			// params
			NodeList nodesParam = root.getElementsByTagName("param");
			if(nodesParam != null && nodesParam.getLength() > 0)
			{
				for (int p = 0; p < nodesParam.getLength(); p++) {
					Node nodeParam = nodesParam.item(p);
					
					Node nodeParamValue = nodeParam.getLastChild();
					if (nodeParamValue.getNodeType() == Node.ELEMENT_NODE
							&& nodeParamValue.getNodeName().equals("value")) {
						
						Node nodeParamValueObject = nodeParamValue.getLastChild();
						if (nodeParamValueObject.getNodeType() == Node.ELEMENT_NODE
								&& nodeParamValueObject.getNodeName().equals("object")) {
							
							ctClass  = GetCtClassByNodeObject(nodeParamValueObject, ctClass);
							
						}
					}
					
				}
			}
			Class<?> cl = ctClass.toClass();
			
			// to generate the method's response
			String methodResponse = this.GenerateMethodReponse(appelMethod,cl);
			
			System.out.println(methodResponse);
			
			return methodResponse;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

// endregion
}
